#include <string.h>
#include <assert.h>
#include "fontmgr_impl.h"

namespace visgrph
{

formatted_text_impl::formatted_text_impl()
: m_drawtext_flags((7 << 27) | dt_left | dt_top)
, m_char_spacing(0), m_height_spacing(0)
{
	u.content = NULL;
	m_required_width = m_required_height = -1;
}

formatted_text_impl::~formatted_text_impl()
{
	release_text_formatting();
}

bool formatted_text_impl::text(const char* content, bool bclone)
{
	release_text_formatting();
	if (NULL == content || *content == '\0')
		return true;

	if (need_formatting())
	{
		if (testflags(ftf_switch_formatting))
		{
			assert(!testflags(ftf_formatted));
			if (!testflags(ftf_immd_content) && u.content)
				delete []u.content;
			u.extfmting = new extend_format(this, float(m_required_width));
		}
		
		clearflags(ftf_switch_formatting
			| ftf_paragraph_reformatting);

		if (NULL == u.extfmting || !u.extfmting->do_format(content))
		{
			if (u.extfmting) delete u.extfmting;
			clearflags(ftf_formatted);
			u.extfmting = NULL;
			return false;
		}
		setflags(ftf_formatted);
	}
	else if (bclone)
	{
		u.content = new char [strlen(content) + 1];
		if (NULL == u.content)
			return false;

		strcpy(u.content, content);
		clearflags(ftf_immd_content);
	}
	else
	{
		u.content = (char*)content;
		setflags(ftf_immd_content);
	}
	return true;
}

bool formatted_text_impl::execute_text_formatting(void)
{
	bool ret = true;
	if (testflags(ftf_switch_formatting))
		ret = switch_text_formatting();
	else if (testflags(ftf_paragraph_reformatting))
		ret = (u.extfmting) ? u.extfmting->reformat() : false;

	clearflags(ftf_switch_formatting
		| ftf_paragraph_reformatting);
	return ret;
}

bool formatted_text_impl::switch_text_formatting(void)
{
	const char *text = u.content;
	extend_format* fmt = new extend_format(this, float(m_required_width));
	if (NULL == fmt) return false;

	if (!fmt->do_format(text))
	{
		delete fmt;
		return false;
	}
	u.extfmting = fmt;
	setflags(ftf_formatted);

	if (!testflags(ftf_immd_content))
		delete [] text;
	else clearflags(ftf_immd_content);
	return true;
}

bool formatted_text_impl::set_alignment(uint flags)
{
	if (flags & ~(dt_left | dt_center | dt_right
			| dt_top | dt_middle | dt_bottom))
		return false;

	uint flag = 0;
	bool prev = need_formatting();
	
	if (flags & dt_left) flag = dt_left;
	else if (flags & dt_center) flag = dt_center;
	else if (flags & dt_right) flag = dt_right;
	if (flag)
	{
		clearflags(dt_left | dt_center | dt_right);
		setflags(flag);
	}

	flag = 0;
	if (flags & dt_top) flag = dt_top;
	else if (flags & dt_middle) flag = dt_middle;
	else if (flags & dt_bottom) flag = dt_bottom;
	if (flag)
	{
		clearflags(dt_top | dt_middle | dt_bottom);
		setflags(flag);
	}

	if (need_formatting() && !prev)
		setflags(ftf_switch_formatting);
	return true;
}

bool formatted_text_impl::wordwrap(bool w)
{
	bool prev = wordwrap();
	if (prev == w) return w;

	prev = need_formatting();

	// set the latest wordwrap
	if (w) m_drawtext_flags |= dt_wordwrap;
	else m_drawtext_flags &= ~dt_wordwrap;

	if (w)	// enable the wordwrap
	{
		if (need_formatting())
		{
			if (!prev) // change from normal mode to format mode
				setflags(ftf_switch_formatting);
		}
	}
	// todo:
	return w;
}

bool formatted_text_impl::set_wrapping_rect(int w, int h)
{
	bool changed = false;
	bool prev = need_formatting();
	if (w > 0 && w != m_required_width)
		m_required_width = w, changed = true;
	if (h > 0 && h != m_required_height)
		m_required_height = h, changed = true;
	if (!changed) return false;

	if (need_formatting())
	{
		if (!prev) setflags(ftf_switch_formatting);
		else setflags(ftf_paragraph_reformatting);
	}
	return true;
}

bool formatted_text_impl::flags_sanity_check(void)
{
	// todo:
	return true;
}

bool formatted_text_impl::need_formatting(void)
{
	return testflags(
		dt_wordwrap | dt_calcrect
		| dt_center | dt_right
		| dt_middle | dt_bottom
		| dt_end_ellipsis) ? true : false;
}

void formatted_text_impl::release_text_formatting(void)
{
	if (testflags(ftf_formatted))
		delete u.extfmting;
	else if (!testflags(ftf_immd_content))
		delete [] u.content;
	else clearflags(ftf_immd_content);
	u.content = NULL;
}

bool formatted_text_impl::check_rect(void)
{
	if (testflags(dt_wordwrap | dt_center | dt_right
		| dt_end_ellipsis) && m_required_width <= 0)
		return false;
	if (testflags(dt_middle | dt_bottom) && m_required_height <= 0)
		return false;
	return true;
}

extend_format::extend_format(formatted_text_impl* fi, float required_width)
: m_fti(fi)
, m_required_width(required_width)
, m_pgph_table(NULL)
, m_pgph_count_limit(0)
, m_pgph_count(0)
, m_line_count(0)
{
	assert(NULL != fi);
	if (m_required_width < 0)
		m_required_width = _FLT_MAX;
}

extend_format::~extend_format()
{
	for (uint i = 0; i < m_pgph_count; ++i)
		delete m_pgph_table[i];
	delete [] m_pgph_table;
}

extformat_allocator::extformat_allocator()
{
	listnode_init(free_fmt_paragraph_obj_list);
	listnode_init(free_fmt_paragraph_line_obj_list);
}

extformat_allocator::~extformat_allocator()
{
	while (!listnode_isempty(free_fmt_paragraph_line_obj_list))
	{
		listnode_t* next = free_fmt_paragraph_line_obj_list.next;
		extfmt_freeobject *obj = list_entry(extfmt_freeobject, ownerlist, next);
		listnode_del(obj->ownerlist);
		delete reinterpret_cast<fmt_paragraph_line*>(obj);
	}
	while (!listnode_isempty(free_fmt_paragraph_obj_list))
	{
		listnode_t* next = free_fmt_paragraph_obj_list.next;
		extfmt_freeobject *obj = list_entry(extfmt_freeobject, ownerlist, next);
		listnode_del(obj->ownerlist);
		delete reinterpret_cast<fmt_paragraph*>(obj);
	}
}

fmt_paragraph* extformat_allocator::fmt_paragrpah_alloc(extend_format* eft)
{
	fmt_paragraph* ret;
	if (listnode_isempty(free_fmt_paragraph_obj_list))
		ret = new fmt_paragraph(eft);
	else
	{
		listnode_t* next = free_fmt_paragraph_obj_list.next;
		extfmt_freeobject *obj = list_entry(extfmt_freeobject, ownerlist, next);
		listnode_del(obj->ownerlist);
		ret = reinterpret_cast<fmt_paragraph*>(obj);
	}
	return ret;
}

fmt_paragraph_line* extformat_allocator::fmt_paragraph_line_alloc(void)
{
	fmt_paragraph_line* ret;
	if (listnode_isempty(free_fmt_paragraph_line_obj_list))
		ret = new fmt_paragraph_line();
	else
	{
		listnode_t* next = free_fmt_paragraph_line_obj_list.next;
		extfmt_freeobject *obj = list_entry(extfmt_freeobject, ownerlist, next);
		listnode_del(obj->ownerlist);
		ret = reinterpret_cast<fmt_paragraph_line*>(obj);
	}
	return ret;
}

void extformat_allocator::fmt_paragraph_free(fmt_paragraph* p)
{
	if (NULL == p) return;
	p->destroy();
	extfmt_freeobject *obj = reinterpret_cast<extfmt_freeobject*>(p);
	listnode_add(free_fmt_paragraph_obj_list, obj->ownerlist);
}

void extformat_allocator::fmt_paragraph_line_free(fmt_paragraph_line* p)
{
	if (NULL == p) return;
	extfmt_freeobject *obj = reinterpret_cast<extfmt_freeobject*>(p);
	listnode_add(free_fmt_paragraph_line_obj_list, obj->ownerlist);
}

fmt_paragraph::fmt_paragraph(extend_format* eft)
: extfmt(eft)
, content(NULL)
, content_length(0)
, line_count_limit(0)
, line_count(0)
, first_line(0)
, line_table(NULL)
{
}

void fmt_paragraph::destroy(bool dtor)
{
	assert(extfmt != NULL);

	if (dtor)
	{
		if (content) delete [] content;
		content = NULL;
		content_length = 0;
	}
	else if (content) content[0] = '\0';

	for (uint i = 0; i < line_count; ++i)
	{
		extfmt->fmt_paragraph_line_free(line_table[i]);
		line_table[i] = NULL;
	}
	line_count = 0;

	if (dtor)
	{
		if (line_table) delete [] line_table;
		line_table = NULL;
		line_count_limit = 0;
	}
}

static uint pow2rnd(uint val)
{
	uint cnt, ret = 1;
	for (cnt = 0; val; val >>= 1, ret <<= 1)
		cnt += (val & 1);
	if (cnt == 1) ret >>= 1;
	return ret;
}

#define PARAGRAPH_LINE_MIN		(64)

void fmt_paragraph::copy_line_table(fmt_paragraph_line** tbl, uint start, uint cnt)
{
	assert(NULL != tbl);
	uint lc = start + cnt;
	if (line_count < lc) line_count = lc;
	if (line_count_limit < lc)
	{
		line_table = (fmt_paragraph_line**)realloc(line_table,
			lc * sizeof(fmt_paragraph_line*));

		assert(NULL != line_table);
		memset(&line_table[line_count_limit], 0, sizeof(fmt_paragraph_line*)
			* (lc - line_count_limit));
		line_count_limit = lc;	
	}
	memcpy(&line_table[start], tbl, cnt * sizeof(fmt_paragraph_line*));
}

void fmt_paragraph::copy_extend_line_table(fmt_paragraph_line** tbl, uint start, uint cnt)
{
	assert(NULL != tbl);
	uint lc = start + cnt;
	if (line_count < lc) line_count = lc;
	if (line_count_limit < lc)
	{
		line_count_limit = pow2rnd(lc);
		check_extend_line_table();
	}
	memcpy(&line_table[start], tbl, cnt * sizeof(fmt_paragraph_line*));
}

void fmt_paragraph::check_extend_line_table(void)
{
	if (line_count < line_count_limit)
		return;

	// need to expend the line table
	const int lntbl_phase1_max = 4096;
	const int lntbl_phase2_delta = 1024;

	uint prev_line_cnt_limit = line_count_limit;
	if (line_count_limit < PARAGRAPH_LINE_MIN)
		line_count_limit = PARAGRAPH_LINE_MIN;
	else if (line_count_limit < lntbl_phase1_max)
		line_count_limit *= 2;
	else line_count_limit += lntbl_phase2_delta;

	line_table = (fmt_paragraph_line**)realloc(line_table,
		line_count_limit * sizeof(fmt_paragraph_line*));

	assert(NULL != line_table);
	memset(&line_table[prev_line_cnt_limit], 0, sizeof(fmt_paragraph_line*)
		* (line_count_limit - prev_line_cnt_limit));
}

void fmt_paragraph::append_line(fmt_paragraph_line* line)
{
	assert(NULL != line);
	check_extend_line_table();
	line_table[line_count++] = line;
}

// reformatting a paragraph
bool fmt_paragraph::paragraph_reformatting(float& width_max)
{
	assert(NULL != content && NULL != extfmt);
	const char* eol;
	const char* start = content;

	uint lines = 0;
	fmt_paragraph_line* buffer[PARAGRAPH_LINE_MIN];
	width_max = 0.;
	
	do
	{
		float width = (extfmt->m_fti->testflags(dt_wordwrap))
			? extfmt->required_width() : _FLT_MAX;
		eol = extfmt->m_fti->truncate(start, -10000, width);

		fmt_paragraph_line* line = NULL;
		if (lines < line_count)
			line = line_table[lines];
		if (NULL == line)
		{
			line = extfmt->fmt_paragraph_line_alloc();
			if (NULL == line)
			{
				destroy();
				return false;
			}
		}

		line->start = start - content;
		line->length = eol - start;
		line->width = width;

		if (lines >= PARAGRAPH_LINE_MIN)
		{
			if (lines == PARAGRAPH_LINE_MIN)
				copy_extend_line_table(buffer, 0, PARAGRAPH_LINE_MIN);
			append_line(line);
		}
		else buffer[lines] = line;

		// check the max width
		if (width_max < width) width_max = width;
		start = eol;
		++lines;
	}
	while (*eol != '\n' && *eol != '\0');

	// flush buffers if necessary
	if (lines < PARAGRAPH_LINE_MIN)
		copy_line_table(buffer, 0, lines);

	// reset the line count
	line_count = lines;
	return true;
}

// build a new paragraph
const char* fmt_paragraph::paragraph_formatting(const char* text, float& width_max)
{
	assert(NULL != text && NULL != extfmt);
	const char* eol;
	const char* start = text;

	uint lines = 0;
	fmt_paragraph_line* buffer[PARAGRAPH_LINE_MIN];
	width_max = 0.;
	
	// release original if necessary
	destroy();

	do
	{
		float width = (extfmt->m_fti->testflags(dt_wordwrap))
			? extfmt->required_width() : _FLT_MAX;
		eol = extfmt->m_fti->truncate(start, -10000, width);

		fmt_paragraph_line* line = extfmt->fmt_paragraph_line_alloc();
		if (NULL == line)
		{
			destroy();
			return NULL;
		}

		line->start = start - text;
		line->length = eol - start;
		line->width = width;

		if (lines >= PARAGRAPH_LINE_MIN)
		{
			if (lines == PARAGRAPH_LINE_MIN)
				copy_extend_line_table(buffer, 0, PARAGRAPH_LINE_MIN);
			append_line(line);
		}
		else buffer[lines] = line;

		// check the max width
		if (width_max < width) width_max = width;
		start = eol;
		++lines;
	}
	while (*eol != '\n' && *eol != '\0');

	// flush buffers if necessary
	if (lines < PARAGRAPH_LINE_MIN)
		copy_line_table(buffer, 0, lines);

	// allocate the size for content
	new_content(eol - text);
	if (NULL == content)
	{
		destroy();
		return NULL;
	}

	// save the content
	if (text != eol)
	{
		memcpy(content, text, eol - text);
		content[eol - text] = '\0';
	}
	return (*eol == '\n') ? eol + 1 : eol;
}

void extend_format::copy_paragraph_table(fmt_paragraph** tbl, uint cnt)
{
	assert(NULL != tbl);
	m_pgph_count = cnt;
	if (m_pgph_count_limit < cnt)
	{
		m_pgph_count_limit = cnt;
		m_pgph_table = (fmt_paragraph**)realloc(m_pgph_table,
			m_pgph_count_limit * sizeof(fmt_paragraph*));
		assert(NULL != m_pgph_table);
	}
	memcpy(m_pgph_table, tbl, cnt * sizeof(fmt_paragraph*));
}

void extend_format::copy_extend_paragraph_table(fmt_paragraph** tbl, uint cnt)
{
	assert(NULL != tbl);
	m_pgph_count = cnt;
	if (m_pgph_count_limit < cnt)
	{
		m_pgph_count_limit = pow2rnd(cnt);
		check_extend_paragraph_table();
	}
	memcpy(m_pgph_table, tbl, cnt * sizeof(fmt_paragraph*));
}

#define PARAGRAPH_MIN		(64)

void extend_format::check_extend_paragraph_table(void)
{
	if (m_pgph_count < m_pgph_count_limit)
		return;

	// need to expend the line table
	const int pgphtbl_phase1_max = 4096;
	const int pgphtbl_phase2_delta = 1024;

	uint prev_pgph_cnt_limit = m_pgph_count_limit;
	if (m_pgph_count_limit < PARAGRAPH_MIN)
		m_pgph_count_limit = PARAGRAPH_MIN;
	else if (m_pgph_count_limit < pgphtbl_phase1_max)
		m_pgph_count_limit *= 2;
	else m_pgph_count_limit += pgphtbl_phase2_delta;

	m_pgph_table = (fmt_paragraph**)realloc(m_pgph_table,
		m_pgph_count_limit * sizeof(fmt_paragraph*));

	assert(NULL != m_pgph_table);
	memset(&m_pgph_table[prev_pgph_cnt_limit], 0, sizeof(fmt_paragraph*)
		* (m_pgph_count_limit - prev_pgph_cnt_limit));
}

void extend_format::append_paragraph(fmt_paragraph* p)
{
	assert(NULL != p);
	check_extend_paragraph_table();
	m_pgph_table[m_pgph_count++] = p;	
}

bool extend_format::reformat(void)
{
	uint lines = 0;
	bool ret = true;

	for (uint i = 0; i < m_pgph_count; ++i)
	{
		float width;
		fmt_paragraph* p = m_pgph_table[i];
		if (NULL == p)
		{
			ret = false;
			continue;
		}
		if (!p->paragraph_reformatting(width))
			ret = false;

		p->set_first_line(lines);
		lines += p->get_line_count();
	}
	m_line_count = lines;
	return ret;
}

bool extend_format::do_format(const char* text)
{
	if (NULL == text) return true;

	uint lines = 0;
	uint paragraphs = 0;
	fmt_paragraph* buffer[PARAGRAPH_MIN];

	for (; *text; ++paragraphs)
	{
		fmt_paragraph *p = NULL;
		if (paragraphs < m_pgph_count)
			p = m_pgph_table[paragraphs];
		if (NULL == p) p = fmt_paragrpah_alloc(this);
		if (NULL == p) return false;

		float width;
		text = p->paragraph_formatting(text, width);
		if (NULL == text)
		{
			fmt_paragraph_free(p);
			return false;
		}

		p->set_first_line(lines);
		lines += p->get_line_count();

		if (paragraphs >= PARAGRAPH_MIN)
		{
			if (paragraphs == PARAGRAPH_MIN)
				copy_extend_paragraph_table(buffer, PARAGRAPH_MIN);
			append_paragraph(p);
		}
		else buffer[paragraphs] = p;
	}
	// flush buffers if necessary
	if (paragraphs < PARAGRAPH_MIN)
		copy_paragraph_table(buffer, paragraphs);
	m_line_count = lines;
	return true;
}

int extend_format::find_paragraph_index_by_line_nubmer(uint line)
{
	if (line >= m_line_count)
		return -1;

	uint mid;
	uint start = 0;
	uint end = m_pgph_count - 1;

	while (start <= end)
	{
		mid = (start + end) / 2;
		fmt_paragraph* tmp = m_pgph_table[mid];
		uint firstline = tmp->get_first_line();
		uint lastline = firstline + tmp->get_line_count();

		if (line < firstline) end = mid - 1;
		else if (line >= lastline) start = mid + 1;
		else return int(mid);
	}
	return -2;
}

void line_iter::reset(void)
{
	line_number = paragraph = 0;
	line = NULL;
}

bool extend_format::line(uint line, line_iter& iter)
{
	iter.paragraph = find_paragraph_index_by_line_nubmer(line);
	if (iter.paragraph < 0)
	{
		iter.reset();
		return false;
	}
	iter.line_number = line;
	iter.line = m_pgph_table[iter.paragraph]->line(line);
	return true;
}

bool extend_format::next_line(line_iter& iter)
{
	// move to next line
	++iter.line_number;

	while (iter.paragraph < m_pgph_count)
	{
		fmt_paragraph* tmp = m_pgph_table[iter.paragraph];

		if (iter.line_number < tmp->get_first_line())
			return line(iter.line_number, iter);
		iter.line = tmp->line(iter.line_number);
		if (iter.line) return true;
		++iter.paragraph;
	}
	return false;
}

}; // end of namespace visgrph
/* EOF */
